package com.example.networktest.utils;

import android.content.Context;
import android.os.Handler;

import androidx.annotation.NonNull;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;

import okhttp3.Cache;
import okhttp3.Call;
import okhttp3.CookieJar;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.JavaNetCookieJar;
import okhttp3.Callback;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.FormBody;

/**
 * 网络管理类
 *
 * @author chen990627
 * @date 2022/4/8
 */

public class OkHttpManager {

    /**
     * mediaType 这个需要和服务端保持一致
     */
    private static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8");
    private static final MediaType MEDIA_TYPE_FORM = MediaType.parse("multipart/form-data");
    
    /**
     * 图片上传使用
     */
    private static final MediaType MEDIA_TYPE_FILE = MediaType.parse("image/*");

    /**
     * 单例引用
     */
    private static volatile OkHttpManager mOkHttpManager = null;

    /**
     * okHttpClient 实例
     */
    private static OkHttpClient mOkHttpClient = null;

    private static OkHttpClient.Builder mBuilder = null;

    /**
     * 全局处理子线程和主线程通信
     */
    private Handler okHttpHandler;

    private Context mContext;

    private static final int CACHE_SIZE = 10 << 20;

    /**
     * 初始化OkHttpManager
     */
    private OkHttpManager(Context context) {

        mBuilder = getHttpSingleBuilderInstance();

        //开启响应缓存
        CookieJar cookieJar = new JavaNetCookieJar(new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER));
        mBuilder.cookieJar(cookieJar);
        //设置缓存目录和大小
        Cache cache = new Cache(context.getCacheDir(), CACHE_SIZE);
        mBuilder.cache(cache);
        //设置合理的超时
        mBuilder.connectTimeout(15, TimeUnit.SECONDS);
        mBuilder.readTimeout(20, TimeUnit.SECONDS);
        mBuilder.writeTimeout(20, TimeUnit.SECONDS);
        //以下验证不设置，那么就默认已经设置了验证
        mBuilder.hostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String s, SSLSession sslSession) {
                return true;
            }
        });
        mOkHttpClient = mBuilder.build();
    }

    public static OkHttpManager getOkHttpManager(Context context) {

        if (mOkHttpManager == null) {
            synchronized (OkHttpManager.class) {
                if (mOkHttpManager == null) {
                    mOkHttpManager = new OkHttpManager(context);
                }
            }
        }
        return mOkHttpManager;
    }

    public static OkHttpClient.Builder getHttpSingleBuilderInstance() {

        if (mBuilder == null) {
            synchronized (OkHttpClient.class) {
                mBuilder = new OkHttpClient.Builder();
            }
        }
        return mBuilder;
    }

    ///////////////////////////////////////////////////////////////////////////
    // GET方式网络访问
    ///////////////////////////////////////////////////////////////////////////
    /**
     * 基方法，返回Request对象
     *
     * @param urlString
     * @param tag
     * @return
     */
    private Request buildGetRequest(String urlString, Object tag) {

        Request.Builder builder = new Request.Builder();
        builder.url(urlString);
        if (tag != null) {
            builder.tag(tag);
        }
        return builder.build();
    }

    /**
     * 自定义方法，返回Response对象
     *
     * @param urlString
     * @return
     * @throws IOException
     */
    private Response buildResponse(String urlString, Object tag) throws IOException {

        Request request = buildGetRequest(urlString, tag);
        Response response = mOkHttpClient.newCall(request).execute();
        return response;
    }

    //基础方法，返回ResponseBody对象
    private ResponseBody buildResponseBody(String urlString, Object tag) throws IOException {

        Response response = buildResponse(urlString, tag);
        if (response.isSuccessful()) {
            return response.body();
        }
        return null;
    }

    /**
     * 作用：实现网络访问文件，将获取到数据储存在文件流中
     *
     * @param urlString ：访问网络的url地址
     * @return InputStream
     */
    public static InputStream getStreamFromURL(Context context, String urlString, Object tag) throws IOException {

        ResponseBody body = getOkHttpManager(context).buildResponseBody(urlString, tag);
        if (body != null) {
            return body.byteStream();
        }
        return null;
    }

    /**
     * 作用：实现网络访问文件，将获取到的数据存在字节数组中
     *
     * @param urlString ：访问网络的url地址
     * @return byte[]
     */
    public static byte[] getBytesFromURL(Context context, String urlString, Object tag) throws IOException {

        ResponseBody body = getOkHttpManager(context).buildResponseBody(urlString, tag);
        if (body != null) {
            return body.bytes();
        }
        return null;
    }

    /**
     * 作用：实现网络访问文件，将获取到的数据存在字符串中
     *
     * @param urlString ：访问网络的url地址
     * @return String
     */
    public static String getStringFromURL(Context context, String urlString, Object tag) throws IOException {

        ResponseBody body = getOkHttpManager(context).buildResponseBody(urlString, tag);
        if (body != null) {
            body.string();
        }
        return null;
    }

    ///////////////////////////////////////////////////////////////////////////
    // POST方式访问网络
    ///////////////////////////////////////////////////////////////////////////
    /**
     * 基方法，返回Request对象
     *
     * @param urlString
     * @param tag
     * @return
     */
    private Request buildPostRequest(String urlString, RequestBody requestBody, Object tag) {

        Request.Builder builder = new Request.Builder();
        builder.url(urlString).post(requestBody);
        if (tag != null) {
            builder.tag(tag);
        }
        return builder.build();
    }

    /**
     * 作用：post提交数据，返回服务器端返回的str
     *
     * @param urlString ：访问网络的url地址
     * @return String
     */
    private String postRequestBody(String urlString, RequestBody requestBody, Object tag) {

        Request request = buildPostRequest(urlString, requestBody, tag);
        try {
            Response response = mOkHttpClient.newCall(request).execute();
            if (response.isSuccessful()) {
                return response.body().string();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 作用：POST提交键值对，再返回相应的数据
     *
     * @param urlString ：访问网络的url地址
     * @param requestMap       ：访问url时，需要传递给服务器的键值对数据。
     * @return String
     */
    public static String postKeyValuePair(Context context, String urlString, Map<String, String> requestMap, Object tag) {
        //往FormBody对象中放置键值对
        FormBody.Builder formBuilder = new FormBody.Builder();
        if (requestMap != null && !requestMap.isEmpty()) {
            for (Map.Entry<String, String> entry : requestMap.entrySet()) {
                formBuilder.add(entry.getKey(), entry.getValue());
            }
        }
        RequestBody requestBody = formBuilder.build();
        return getOkHttpManager(context).postRequestBody(urlString, requestBody, tag);
    }

    /**
     * 作用：POST提交Json字符串，再返回相应的数据
     *
     * @param urlString  ：访问网络的url地址
     * @param jsonString ：访问url时，需要传递给服务器的json字符串
     * @return byte[]
     */
    public static String postJsonString(Context context, String urlString, String jsonString, Object tag) {

        RequestBody requestBody = RequestBody.create(jsonString, MEDIA_TYPE_JSON);
        return getOkHttpManager(context).postRequestBody(urlString, requestBody, tag);
    }

    ///////////////////////////////////////////////////////////////////////////
    // 异步网络访问
    ///////////////////////////////////////////////////////////////////////////
    /**
     * 开启异步线程访问网络，通过回调方法实现数据加载
     * 如果第二个参数为null，空callback， 则说明不在意返回结果
     *
     * @param urlString
     * @param callback
     */
    public static void getDataAsync(Context context, String urlString, Callback callback, Object tag) {

        Request request = getOkHttpManager(context).buildGetRequest(urlString, tag);
        getHttpSingleBuilderInstance().build().newCall(request).enqueue(callback);
    }

    /**
     * 作用：post提交数据，返回服务器端返回的字节数组
     *
     * @param urlString ：访问网络的url地址
     */
    private void postRequestBodyAsync(String urlString, RequestBody requestBody, Callback callback, Object tag) {

        Request request = buildPostRequest(urlString, requestBody, tag);
        if (callback == null) {
            new Callback() {

                @Override
                public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {

                }

                @Override
                public void onFailure(@NonNull Call call, @NonNull IOException e) {

                }
            };
        }
        mOkHttpClient.newCall(request).enqueue(callback);
    }

    /**
     * 作用：POST提交键值对，再返回相应的数据
     *
     * @param urlString ：访问网络的url地址
     * @param requestMap       ：访问url时，需要传递给服务器的键值对数据。
     */
    public static void postKeyValuePairAsync(Context context, String urlString, Map<String, String> requestMap, Callback callback, Object tag) {

        FormBody.Builder formBuilder = new FormBody.Builder();
        if (requestMap != null && !requestMap.isEmpty()) {
            for (Map.Entry<String, String> entry : requestMap.entrySet()) {
                formBuilder.add(entry.getKey(), entry.getValue());
            }
        }
        RequestBody requestBody = formBuilder.build();
        getOkHttpManager(context).postRequestBodyAsync(urlString, requestBody, callback, tag);
    }

    /**
     * 作用：POST异步提交Json字符串，再返回相应的数据
     *
     * @param urlString  ：访问网络的url地址
     * @param jsonString ：访问url时，需要传递给服务器的json字符串
     * @return byte[]
     */
    public static void postJsonStringAsync(Context context, String urlString, String jsonString, Callback callback, Object tag) {

        RequestBody requestBody = RequestBody.create(jsonString, MEDIA_TYPE_JSON);
        getOkHttpManager(context).postRequestBodyAsync(urlString, requestBody, callback, tag);
    }

    ///////////////////////////////////////////////////////////////////////////
    // POST方式提交分块请求，实现文件上传
    ///////////////////////////////////////////////////////////////////////////
    /**
     * 作用：post异步上传文件，提交分块请求
     *
     * @param urlString     网络地址
     * @param requestMap    提交给服务器的表单信息键值对
     * @param files         提交的文件
     * @param formFieldName 每个需要提交的文件对应的文件input的name值
     * @param callback      异步上传回调方法
     * @throws IOException
     */
    public static void postUploadFilesAsync(Context context, String urlString, Map<String, String> requestMap, File[] files, String[] formFieldName, Callback callback, Object tag) throws IOException {

        RequestBody requestBody = getOkHttpManager(context).buildFormRequestBody(requestMap, files, formFieldName);
        getOkHttpManager(context).postRequestBodyAsync(urlString, requestBody, callback, tag);
    }

    /**
     * 同步基于post的文件上传:上传多个文件以及携带key-value对：主方法
     *
     * @param urlString
     * @param formFiledName
     * @param files
     * @param requestMap
     * @param tag
     * @return String
     * @throws IOException
     */
    public static String postUploadFiles(Context context, String urlString, Map<String, String> requestMap, File[] files, String[] formFiledName, Object tag) throws IOException {

        RequestBody requestBody = getOkHttpManager(context).buildFormRequestBody(requestMap, files, formFiledName);
        return getOkHttpManager(context).postRequestBody(urlString, requestBody, tag);
    }

    /**
     * 创建post上传附件的request对象
     * Post方式提交分块请求——上传文件及其它表单数据
     *
     * @param files
     * @param formFiledName
     * @param requestMap
     * @return
     */
    private RequestBody buildFormRequestBody(Map<String, String> requestMap, File[] files, String[] formFiledName) {

        MultipartBody.Builder multipartBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
        //往MultipartBody对象中添加普通input控件的内容
        if (requestMap != null) {
            for (Map.Entry<String, String> entry : requestMap.entrySet()) {
                multipartBuilder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + entry.getKey() + "\""),
                        RequestBody.create(entry.getValue(), null));
            }
        }
        //往MultiBody对象中添加file input控件的内容
        if (files != null && formFiledName != null) {
            for (int i = 0; i < files.length; i++) {
                File file = files[i];
                String fileName = file.getName();
                RequestBody requestBody = RequestBody.create(file, MEDIA_TYPE_FORM);
                //添加file input块的数据
                multipartBuilder.addPart(Headers.of("Content-Disposition",
                        "form-data; name=\"" + formFiledName[i] + "\"; filename=\"" + fileName +
                                "\""), requestBody);
            }
        }
        return multipartBuilder.build();
    }

    /**
     * 获取Mime类型
     *
     * @param fileName
     * @return
     */
    private static String getMimeType(String fileName) {
        FileNameMap fileNameMap = URLConnection.getFileNameMap();
        String contentTypeFor = fileNameMap.getContentTypeFor(fileName);
        if (contentTypeFor == null) {
            contentTypeFor = "application/octet-stream";
        }
        return contentTypeFor;
    }
}
